package com.yudean.esb.util;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.basic.DateConverter;
import com.thoughtworks.xstream.converters.reflection.SunUnsafeReflectionProvider;
import com.thoughtworks.xstream.io.xml.StaxDriver;
import com.yudean.esb.domain.erp.IdsPmProAppr;
import com.yudean.esb.domain.erp.IdsPmProTech;
import com.yudean.esb.exception.InvalidateXmlContentException;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.*;
@Slf4j
@SuppressWarnings({"all"})
public class XmlUtil {
    public static String parserXml(String xmlStr) {

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db;
        try {
            db = dbf.newDocumentBuilder();
            Document document = db.parse(new InputSource(new StringReader(xmlStr)));

            NodeList list = document.getElementsByTagName("datalist");
            StringBuffer results = new StringBuffer();
            if (list.getLength() > 0) {
                Node datalistNode = list.item(0);
                NodeList dataNodes = document.getElementsByTagName("data");//datalistNode.getChildNodes();
                results.append("一共返回记录数量：" + dataNodes.getLength() + "\n");
                for (int i = 0; i < dataNodes.getLength(); i++) {
                    Node dataNode = dataNodes.item(i);
                    NodeList data = dataNode.getChildNodes();
                    if (i == 0) {
                        for (int k = 0; k < data.getLength(); k++) {
                            results.append("	" + data.item(k).getNodeName() + ":" + data.item(k).getTextContent() + "\n");
                        }
                    }
                }
            } else {
                results.append("未返回任何记录");
            }
            return results.toString();
        } catch (ParserConfigurationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SAXException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;


    }

    // serviceName: refer to ServiceConstant
    // serviceType: refer to ServiceConstant
    public static String returnInfo(String serviceName, String serviceType, String info) {
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                "<service>" +
                "<name>"+serviceName+"</name>" +
                "<type>"+serviceType+"</type>" +
                "<data>" +
                "<!--" + info + "-->" +
                "</data>" +
                "</service>";
    }

    private static XStream xStream = null;

    static {
        xStream = new XStream(new StaxDriver());
        //开启注解配置（别名通过注解在Entity中进行配置）
        xStream.autodetectAnnotations(true);
        xStream.allowTypesByRegExp(new String[]{".*"});

        //注册日期转换，处理 yyyy-MM-dd HH:mm:ss, yyyy-MM-dd格式日期
        xStream.registerConverter(new DateConverter("yyyy-MM-dd HH:mm:ss", null, TimeZone.getTimeZone("GMT+8")));

        NullConverter nullConverter = new NullConverter(xStream.getMapper(), new SunUnsafeReflectionProvider());
        // 将转换器注册到非常低的位置非常重要
        xStream.registerConverter(nullConverter, XStream.PRIORITY_VERY_LOW);
        // 不输出class信息
        xStream.aliasSystemAttribute(null, "class");
        //别名配置
        xStream.alias("data", IdsPmProTech.class);
        xStream.alias("datalist", List.class);
        xStream.alias("service", XmlResult.class);
    }

    /**
     * Bean --> XML
     *
     * @param obj Entity
     * @return String XML字符串
     */
    public static String bean2Xml(Object obj) {
        xStream.processAnnotations(obj.getClass());
        return xStream.toXML(obj);
    }

    /**
     * 为生产项目接口定制化转换方法
     * @param data xml格式的数据集
     * @param clazz 实体类的Class对象，如IdsPmProTech.class
     * @param <T> 泛型
     * @return List
     * 扩展：新增实体类转换需修改tagName和数据类型
     */
    public static <T> ParseResult parseListFromXml(String data,Class<T> clazz) {
        //创建List对象，存储解析XML数据集后得到的实体类对象
        List<T> lists = new ArrayList<>();
        //fieldsInfo存储实体类（如IdsPmProTech）的属性名和属性类型
        Map<String, String> fieldsInfo = getDeclaredFieldsInfo(clazz);
        //获取document对象
        org.jsoup.nodes.Document document = Jsoup.parse(data);
        //新增数据表需在这里新增判断，以tagName来区分
        String tagName = "";
        if(clazz.getTypeName().equals(IdsPmProTech.class.getTypeName())){
            tagName = "idsPmProTech";
        }else if(clazz.getTypeName().equals(IdsPmProAppr.class.getTypeName())){
            tagName = "idsPmProAppr";
        }
        //获取标签名为idsPmProTech的Element对象,相当于ArrayList
        final Elements elements = document.getElementsByTag(tagName);
        //XML数据集包含的记录数
        int totalSize = elements.size();
        if (totalSize == 0){
            log.error("Xml数据集中数据根标签有误，正确的根标签为{}",tagName);
        }
        //记录转换出错的项目ID
        List<String> errIds = new ArrayList<>();

        //遍历
        for (Element element : elements) {
            //elementMap存储IdsPmProTech的属性名和属性值
            HashMap<String, Object> elementMap = new HashMap<>();
            //记录转换出错的属性和值
            Map<String,String> fileMap = new HashMap<>();
            try {
                fieldsInfo.forEach((key, value) -> {
                    String elementValue = getElementText(element, key);
                    try{
                        switch (value) {
                            case "java.math.BigDecimal":
                                elementMap.put(key, StrUtil.hasEmpty(elementValue) ? null : new BigDecimal(elementValue));
                                break;
                            case "java.util.Date":
                                elementMap.put(key, StrUtil.hasEmpty(elementValue) ? null : DateUtil.date(Long.parseLong(elementValue)));
                                break;
                            case "java.lang.Integer":
                                elementMap.put(key, StrUtil.hasEmpty(elementValue) ? null : Integer.parseInt(elementValue));
                                break;
                            default:
                                elementMap.put(key, elementValue);
                                break;
                        }
                    }catch(Exception exception){
                        //日志记录解析出错的标签和值
                        fileMap.put(key,elementValue);
                    }
                });
                if (MapUtil.isNotEmpty(fileMap)){
                    throw new InvalidateXmlContentException();
                }
                try {
                    lists.add(BeanUtil.fillBeanWithMap(elementMap, clazz.getDeclaredConstructor().newInstance(), false));
                } catch (InstantiationException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }catch (InvalidateXmlContentException exception){
                log.error("XML数据集解析错误，TECHID={},解析错误的字段:{}",getElementText(element,"techid"),fileMap.toString());
                errIds.add(getElementText(element,"techprojectcode"));
            }
        }
        if (!StrUtil.hasEmpty(data) && lists.size() == 0){
            log.error("XML数据解析异常，data不为空，转换结果为空");
            throw new InvalidateXmlContentException();
        }
        //解析成功的记录数
        int submitted = lists.size();
        return new ParseResult(totalSize,submitted,errIds, lists);
    }

    /**
     * @param clazz 实体类
     * @return 实体类信息 Map<属性值,属性类型>
     */
    public static Map<String, String> getDeclaredFieldsInfo(Class clazz) {
        Map<String, String> objectMap = new HashMap<>();
        Map<String, PropertyDescriptor> propertyDescriptorMap = BeanUtil.getPropertyDescriptorMap(clazz, false);
        propertyDescriptorMap.forEach((key, value) -> {
            objectMap.put(value.getName(), value.getPropertyType().getTypeName());
        });
        return objectMap;
    }

    /**
     * @param element  元素对象Element
     * @param nodeName 子节点名字
     * @return 节点值
     */
    private static String getElementText(Element element, String nodeName) {
        return element.getElementsByTag(nodeName).text();
    }
}
